package sonic.game.character;

import java.awt.Graphics;

import sonic.game.Game;
import sonic.game.animation.Animation;
import sonic.game.animation.AnimationSet;
import sonic.game.item.GameItemDroppedRing;
import sonic.game.tile.GameProjectile;
import sonic.game.tile.Tile;
import sonic.game.util.Vector2D;

/**
 * 
 * Sonic
 * @Author Richard Greenlees
 * 
 * Our hero. Contains special functions for handling player input and sonic-specific moves like jumping,
 * rolling, spin-dashing etc. 
 * 
 */

public class Sonic extends GameCharacter {

	// Direction the player is holding (left or right)
	public int directionHeld = 0;
	// Is Sonic in the air because the player jumped?
	public boolean bJumping = false;
	// Is the player trying to brake suddenly?
	private boolean bBreaking = false;
	// Am I dead?
	public boolean bDead = false;
	// Is the player trying to duck?
	public boolean bDuck = false;
	// Rings collected
	public int rings = 0;
	// Is Sonic currently rolling on the ground? Affects friction and interaction with enemies
	private boolean bRolling = false;
	// Has Sonic just been hurt? Used to play pain animation and handle temporary invulnerability
	private boolean bHurt = false;
	// How long should Sonic be invulnerable for after being hurt?
	private float hurtTime = 2.0f;
	// How long has Sonic been invulnerable for so far?
	private float hurtTimer = 0.0f;
	// How rapidly per second should Sonic blink while invulnerable?
	private float hurtFlash = 0.1f;
	// How soon until his next blink?
	private float flashTimer = 0.0f;
	// Is Sonic currently blinking (and invisible)?
	private boolean bFlashing = false;
	// While rolling, sonic will experience roughly half the friction
	private float rollFriction = 0.75f;
	// When taking damage, sonic will be knocked backwards
	private boolean bKnockBack = false;
	// Max level is 5. Determines speed when released
	public int spinDashLevel = 0;
	// Sonic will accelerate slower if rolling or falling through the air
	public float rollAccelRate = 1.0f;
	
	// Various sounds to play when doing stuff
	private String jumpSound = "sounds/Sonic_jump.wav";
	private String deathSound = "sounds/Sonic_death.wav";
	private String spinSound = "sounds/Sonic_spin.wav";
	private String ringOutSound = "sounds/Sonic_ringout.wav";
	private String spinOffSound = "sounds/Sonic_spinoff.wav";
	
	public Sonic(Vector2D startPos)
	{
		super(startPos);
		AnimationSet normalAnims = new AnimationSet();
		normalAnims.addAnimation("standAnim", new Animation(0, false, "Characters/Sonic/Sonic_standing.png"));
		normalAnims.addAnimation("runAnim", new Animation(0.6f, true, "Characters/Sonic/Sonic_run_1.png", "Characters/Sonic/Sonic_run_2.png", "Characters/Sonic/Sonic_run_3.png", "Characters/Sonic/Sonic_run_4.png", "Characters/Sonic/Sonic_run_5.png", "Characters/Sonic/Sonic_run_6.png", "Characters/Sonic/Sonic_run_7.png", "Characters/Sonic/Sonic_run_8.png"));
		normalAnims.addAnimation("jumpAnim", new Animation(0, false, "Characters/Sonic/Sonic_jump.png"));
		normalAnims.addAnimation("sprintAnim", new Animation(0.25f, true, "Characters/Sonic/Sonic_sprint_1.png", "Characters/Sonic/Sonic_sprint_2.png", "Characters/Sonic/Sonic_sprint_3.png", "Characters/Sonic/Sonic_sprint_4.png"));
		normalAnims.addAnimation("pushAnim", new Animation(2.0f, true, "Characters/Sonic/Sonic_push_1.png", "Characters/Sonic/Sonic_push_2.png", "Characters/Sonic/Sonic_push_3.png", "Characters/Sonic/Sonic_push_4.png"));
		normalAnims.addAnimation("skidAnim", new Animation(0.5f, true, "Characters/Sonic/Sonic_skid_1.png", "Characters/Sonic/Sonic_skid_2.png"));
		normalAnims.addAnimation("deathAnim", new Animation(0, false, "Characters/Sonic/Sonic_death.png"));
		normalAnims.addAnimation("rollAnim", new Animation(0.05f, true, "Characters/Sonic/Sonic_rolling_1.png", "Characters/Sonic/Sonic_rolling_2.png", "Characters/Sonic/Sonic_rolling_3.png", "Characters/Sonic/Sonic_rolling_4.png"));
		normalAnims.addAnimation("hurtAnim", new Animation(0.25f, true, "Characters/Sonic/Sonic_hurt_1.png", "Characters/Sonic/Sonic_hurt_2.png"));
		normalAnims.addAnimation("duckAnim", new Animation(0.25f, false, "Characters/Sonic/Sonic_duck_1.png", "Characters/Sonic/Sonic_duck_2.png"));
		normalAnims.addAnimation("spinAnim", new Animation(0.05f, true, "Characters/Sonic/Sonic_spin_1.png", "Characters/Sonic/Sonic_spin_2.png", "Characters/Sonic/Sonic_spin_3.png", "Characters/Sonic/Sonic_spin_4.png", "Characters/Sonic/Sonic_spin_5.png"));
		normalAnims.addAnimation("edgeAnim", new Animation(0.5f, true, "Characters/Sonic/Sonic_edge_1.png", "Characters/Sonic/Sonic_edge_2.png", "Characters/Sonic/Sonic_edge_3.png"));
		normalAnims.setCurrentAnimation("standAnim");
		
		addAnimationSet("normalAnims", normalAnims);
		setCurrentAnimSet("normalAnims");
		accelRate=3.0f;

		maxGroundSpeed=180.0f;
		collisionBoxX = 27.0f;
		collisionBoxY = 39.0f;
		setLocation(startPos);
		
		bAlwaysRelevant = true;
		
		updateBounds();
	}
	
	public void die()
	{
		// Don't die again if you're already dead!
		if (!bDead)
		{
			currentAnimationSet.setCurrentAnimation("deathAnim");
			collisionType = eCollisionType.CT_NONE;
			physicsType = ePhysicsType.PHYS_FALLING;
			velocity.x = 0;
			velocity.y = -280;
			bDead = true;
			playSound(deathSound);
		}
	}
	
	/** Process various touch events */
	public void touch(Tile a)
	{		
		if (a instanceof Badnik)
		{
			// If sonic is rolling or jumping when touching the enemy then kill them and don't take damage
			if (!bRolling && !bJumping)
			{
				takeDamage(a, 1);
			}
			else
			{
				((Badnik) a).die();
				if (bJumping)
				{
					velocity.y = -velocity.y;
				}
			}
		}
		// At the moment the only projectiles we have are enemy ones, so just take damage
		else if (a instanceof GameProjectile)
		{
			takeDamage(a, 1);
		}
	}
	

	public void takeDamage(Tile damager, int dmg)
	{
		// If already hurt then don't take damage again
		if (bHurt)
		{
			return;
		}
		
		if (rings == 0)
		{
			die();
		}
		else
		{
			// Create the "ring out" effect. Have sonic knocked backwards and spill his rings everywhere
			Vector2D ringSpawnLoc = new Vector2D();
			ringSpawnLoc.x = location.x;
			ringSpawnLoc.y = location.y - 40.0f;
			for (int i = rings; i > 0; i--)
			{
				GameItemDroppedRing droppedRing = new GameItemDroppedRing(ringSpawnLoc);
				Game.currentMap.pendingTiles.add(droppedRing);
			}
			rings = 0;
			bHurt = true;
			velocity.x = 140.0f * ((bFaceLeft) ? 1.0f : -1.0f);
			velocity.y = -140.0f;
			setLocation(location.x, location.y - 10.0f);
			bKnockBack = true;
			playSound(ringOutSound);
		}
	}
	

	/** Draw Sonic on the screen */
	public void paint(Graphics g)
	{
		// If sonic is flashing then he won't be drawn
		if (!bFlashing)
		{
			super.paint(g);
		}
	}

	
	/** Handles Sonic's movement and physics based on current state and player input */
	public void playerUpdate(double deltaTime)
	{
		float newAccel;
		
		// If Sonic's feet touch the bottom of the level boundaries then instant kill
		if (tileCorners[2].y > Game.currentMap.mapBoundsY)
		{
			die();
			return;
		}
		
		// If Sonic is falling or rolling, he will be less responsive to player input
		newAccel = (bRolling || physicsType == ePhysicsType.PHYS_FALLING) ? rollAccelRate : accelRate;
		
		bBreaking = false;
		
		if (directionHeld == 1 && !bDuck)
		{
			if (velocity.x < maxGroundSpeed)
			{
				// If the player is trying to run into a wall
				if (currentCollisions[RIGHTCOLLISION] != null && currentCollisions[RIGHTCOLLISION].collisionType == eCollisionType.CT_BLOCKALL)
				{
					currentAnimationSet.setCurrentAnimation("pushAnim");
				}
				else
				{
					// Accelerate in the desired direction
					velocity.x += 120*newAccel*Game.deltaTime;
					// If Sonic is already travelling in the opposite direction then apply braking behaviour
					if (velocity.x < 0)
					{
						bBreaking=true;
					}
				}
			}
		}
		else if (directionHeld == -1 && !bDuck)
		{	
			if (velocity.x > -maxGroundSpeed)
			{
				if (currentCollisions[LEFTCOLLISION] != null && currentCollisions[LEFTCOLLISION].collisionType == eCollisionType.CT_BLOCKALL)
				{
					currentAnimationSet.setCurrentAnimation("pushAnim");
				}
				else
				{
					velocity.x -= 120*accelRate*Game.deltaTime;
					if (velocity.x > 0)
					{
						bBreaking=true;
					}
				}
			}
		}
		
		// If Sonic is travelling fast enough and the player ducks, then start rolling
		if (bDuck && Math.abs(velocity.x) >= 12.0f && physicsType == ePhysicsType.PHYS_WALKING && !bRolling)
		{
			bRolling = true;
			friction = rollFriction;
			playSound(spinSound);
		}
		// However, if Sonic is rolling and slows down too much, then stop
		if (bRolling && Math.abs(velocity.x) < 12.0f)
		{
			bRolling = false;
			friction = defaultFriction;
		}
		
		updateAnims(deltaTime);
	}
	
	/** Player has pressed jump and Sonic is able to do so */
	public void doJump()
	{
		if (!bDuck)
		{
			if (Game.player.physicsType == ePhysicsType.PHYS_WALKING)
			{
				// Put Sonic slightly into the air so collision detection doesn't think he's landing
				location.y -= 20;
				// Launch him up into the air
				velocity.y -= 280;
				currentAnimationSet.setCurrentAnimation("jumpAnim");
				bJumping = true;
				bRolling = false;
				// Make sure he comes back down...
				physicsType = ePhysicsType.PHYS_FALLING;
				playSound(jumpSound);
			}
		}
		else
		{
			if (spinDashLevel < 5)
			{
				spinDashLevel++;
			}
			playSound(spinSound);
		}
	}
	
	/** Sonic specific stuff: Flashing if hurt, call playerUpdate if not dead */
	public void update(double deltaTime)
	{
		super.update(deltaTime);
		
		if (bHurt)
		{
			flashTimer += Game.deltaTime;
			hurtTimer += Game.deltaTime;
			if (flashTimer >= hurtFlash)
			{
				bFlashing = !bFlashing;
				flashTimer = 0.0f;
			}
			
			if (hurtTimer > hurtTime)
			{
				bHurt = false;
				bFlashing = false;
				flashTimer = 0.0f;
				hurtTimer = 0.0f;
			}
			
			
		}
		
		// If dead then Sonic will stop responding to player input
		if (!bDead)
		{
			playerUpdate(deltaTime);
		}
	}
	
	/** Blast off! */
	public void endSpinDash()
	{
		// Provide a base speed boost of 380 pixels per second, then an extra 40 for each additional level
		velocity.x = 380.0f;
		velocity.x += 40.0f * spinDashLevel;
		// Don't forget to make sure he zooms in the correct direction!
		velocity.x *= (bFaceLeft) ? -1 : 1;
		bRolling = true;
		spinDashLevel = 0;
		playSound(spinOffSound);
	}
	
	/** Main animation method. Determines which animation should play based on Sonic's state, and updates it */
	public void updateAnims(double deltaTime)
	{
		if (physicsType == ePhysicsType.PHYS_WALKING && Math.abs(velocity.x) > 12.0f)
		{
			if (bBreaking && !bRolling)
			{
				currentAnimationSet.setCurrentAnimation("skidAnim");
			}
			else if (Math.abs(velocity.x) < maxGroundSpeed)
			{
				bFaceLeft = (velocity.x < 0);
				if (!bRolling)
				{
					currentAnimationSet.setCurrentAnimation("runAnim");
				}
				else
				{
					currentAnimationSet.setCurrentAnimation("rollAnim");
				}
				currentAnimationSet.setCurrentAnimRate(Math.abs(velocity.x)/maxGroundSpeed);
			}
			else
			{
				if (!bRolling)
				{
					currentAnimationSet.setCurrentAnimation("sprintAnim");
				}
				else
				{
					currentAnimationSet.setCurrentAnimation("rollAnim");
					currentAnimationSet.setCurrentAnimRate(1.0f);
				}
			}
		}
		else
		{
			if (physicsType == ePhysicsType.PHYS_WALKING && directionHeld == 0)
			{
				if (!bDuck)
				{
					if (!onEdge())
					{
						currentAnimationSet.setCurrentAnimation("standAnim");
					}
					else
					{
						currentAnimationSet.setCurrentAnimation("edgeAnim");
					}
				}
				else
				{
					if (spinDashLevel == 0)
					{
						currentAnimationSet.setCurrentAnimation("duckAnim");
					}
					else
					{
						currentAnimationSet.setCurrentAnimation("spinAnim");
					}
				}
			}
			else
			{
				if (bJumping && !bDead)
				{
					currentAnimationSet.setCurrentAnimation("jumpAnim");
				}
				else if (bKnockBack)
				{
					currentAnimationSet.setCurrentAnimation("hurtAnim");
				}
			}
		}
		currentAnimationSet.update(deltaTime);
	}
	
	public void landedOn(Tile a)
	{
		super.landedOn(a);
		velocity.y = 0;
		physicsType = ePhysicsType.PHYS_WALKING;
		bJumping = false;
		bKnockBack = false;
	}
	
	/** Player has released the duck key and Sonic is currently ducking */
	public void endDuck()
	{
		bDuck = false;
		if (spinDashLevel > 0)
		{
			endSpinDash();
		}
	}
	
	/** If the player is telling Sonic to duck */
	public void beginDuck()
	{
		// Don't allow Sonic to duck if he's not on the ground
		if (physicsType == ePhysicsType.PHYS_WALKING)
		{
			bDuck = true;
		}
	}
	
	/** Run a trace to Sonic's left and right to see if he's on a ledge or not */
	public boolean onEdge()
	{
		Vector2D startTraceLeft, startTraceRight, endTraceRight, endTraceLeft;
		
		// The following will start at his centre, offset it left or right slightly and move down to his feet
		startTraceLeft = getCentre();
		startTraceRight = getCentre();
		
		startTraceLeft.x -= 5.0f;
		startTraceLeft.y = tileCorners[2].y;
		startTraceRight.x += 5.0f;
		startTraceRight.y = tileCorners[2].y;
		
		// Trace down for 10 pixels, we won't want him wobbling on a tiny step!
		endTraceRight = new Vector2D(startTraceRight.x, startTraceRight.y + 10.0f);
		endTraceLeft = new Vector2D(startTraceLeft.x, startTraceLeft.y + 10.0f);
		// Run the actual trace. Takes into account if Sonic if currently facing left or right. If he is facing right and the ledge is to his left then this will return false
		return ((fastTrace(startTraceRight, endTraceRight, false) && !fastTrace(startTraceLeft, endTraceLeft, false) && !bFaceLeft) || (!fastTrace(startTraceRight, endTraceRight, false) && fastTrace(startTraceLeft, endTraceLeft, false) && bFaceLeft));
	}
	
}
